package gis

import (
	"dgo/goutils/utils"
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

type NMEA struct {
	Type         string
	UTCStr       string // UTC 时间，hhmmss.sss，时分秒格式
	B            string // 纬度 ddmm.mmm
	DirB         string // 方向
	L            string // 经度 dddmm.mmmm
	DirL         string // 经度方向
	Quality      string /// GPS状态，0=不可用(FIX NOT valid)，1=单点定位(GPS FIX)，2=差分定位(DGPS)，3=无效PPS，4=实时差分定位（RTK FIX），5=RTK FLOAT，6=正在估算
	NumberOfSVs  string /// 正在使用的卫星数量（00 - 24+）（前导位数不足则补0）
	HDOP         string //  HDOP水平精度因子（0.5 - 99.9）
	Height       string // 天线离海平面的高度，-9999.9到9999.9米
	HeightU      string // 单位
	HeightGeoid  string // 大地椭球面相对海平面的高度（-9999.9到9999.9）
	HeightGeoidU string // 单位：M（米）
	DGPSAge      string // 字段13：差分时间（从接收到差分信号开始的秒数，如果不是差分定位将为空）
	SiteNo       string // 差分站ID号0000 - 1023（前导位数不足则补0，如果不是差分定位将为空）
	CRCCode      string // 校验码

	NMEAStr string
	IsOK    bool
}

var (
	NULLNMEA *NMEA = nil
)

func NewNMEA() *NMEA {
	return &NMEA{}
}

func CheckNMEAIsOK(str string) bool {
	if str[0] != '$' {
		return false
	}

	if len(str) < 3 {
		return false
	}

	if str[len(str)-3] != '*' {
		return false
	}

	// 最后两位校验码
	crcstr := str[len(str)-2:]

	// 丢掉前面$和后三位校验码
	str = str[1 : len(str)-3]
	crc := CRC([]byte(str))
	v := utils.HexToInt(crcstr)
	return crc == byte(v)
}

func (self *NMEA) Clone() (n *NMEA) {
	n = NewNMEA()
	n.ParseText(self.Encode())
	return
}

func (self *NMEA) checkIsOK(str string) {
	if (len(self.CRCCode)) != 2 {
		self.IsOK = false
		return
	}
	crc := CRC([]byte(str))
	v := utils.HexToInt(self.CRCCode)
	self.IsOK = crc == byte(v)
}

func (self *NMEA) GetTime() (time.Time, error) {
	str := time.Now().UTC().Format("20060102 ") + self.UTCStr
	return time.ParseInLocation("20060102 150405", str, time.UTC)
}

func (self *NMEA) SetTime(localTime time.Time) {
	self.UTCStr = localTime.UTC().Format("150405")
}

func (self *NMEA) UpdateUTCTime() {
	self.UTCStr = time.Now().UTC().Format("150405")
}

func (self *NMEA) SetLat(lat float64) {
	v := lat
	if v < 0 {
		v = -v
		self.DirB = "S"
	} else {
		self.DirB = "N"
	}
	self.B = Deg2DDMM(v)
}

/**
 * 偏移纬度, 非精确
 */
func (self *NMEA) OffsetLat(metre float64) {
	self.SetLat(OffsetLatMetre(self.GetLat(), metre))
}

/**
 * 偏移经度, 非精确
 */
func (self *NMEA) OffsetLng(metre float64) {
	self.SetLng(OffsetLngMetre(self.GetLng(), metre))
}

func (self *NMEA) OffsetLatRand(max_metre float64) {
	v1 := float64(rand.Intn(int(max_metre*10000))) / 10000
	self.OffsetLat(v1)
}

func (self *NMEA) OffsetLngRand(max_metre float64) {
	v1 := float64(rand.Intn(int(max_metre*10000))) / 10000
	self.OffsetLng(v1)
}

func (self *NMEA) OffsetHeightRand(max_metre float64) {
	v1 := float64(rand.Intn(int(max_metre*10000))) / 10000
	self.SetHeight(self.GetHeight() + v1)
}

func (self *NMEA) SetHeight(h float64) {
	self.Height = fmt.Sprintf("%.3f", h)
	self.HeightGeoid = "0"
	self.HeightU = "M"
}

func (self *NMEA) SetLng(lng float64) {
	v := lng
	if v < 0 {
		v = -v
		self.DirL = "W"
	} else {
		self.DirL = "E"
	}
	self.L = Deg2DDMM(v)
}

func (self *NMEA) GetLat() float64 {
	if len(self.B) == 0 {
		return 0
	} else {
		v, e := strconv.ParseFloat(self.B, 64)
		if e != nil {
			return 0
		}

		if v == 0 {
			return 0
		}

		v = DDMM2Degree(v)

		if self.DirB == "S" || self.DirB == "s" {
			v = -v
		}
		return utils.Round64(v, 9)
	}
}

func (self *NMEA) GetLng() float64 {
	if len(self.L) == 0 {
		return 0
	} else {
		v, e := strconv.ParseFloat(self.L, 64)
		if e != nil {
			return 0
		}

		if v == 0 {
			return 0
		}

		v = DDMM2Degree(v)

		if self.DirL == "W" || self.DirL == "w" {
			v = -v
		}
		return utils.Round64(v, 9)
	}
}

func (self *NMEA) GetPosi() (lng, lat, H float64) {
	h := utils.StrToFloat64Def(self.Height, 0) + utils.StrToFloat64Def(self.HeightGeoid, 0)
	return self.GetLng(), self.GetLat(), h
}

func (self *NMEA) GetPosiBLH() (b, l, h float64) {
	l, b, h = self.GetPosi()
	return
}

func (self *NMEA) GetHeight() (h float64) {
	h = utils.StrToFloat64Def(self.Height, 0) + utils.StrToFloat64Def(self.HeightGeoid, 0)
	return h
}

func (self *NMEA) GetEcefXyz() (x, y, z float64) {
	h := utils.StrToFloat64Def(self.Height, 0) + utils.StrToFloat64Def(self.HeightGeoid, 0)
	return Pos2Ecef(self.GetLat(), self.GetLng(), h)
}

func (self *NMEA) EncodeBuf() []byte {
	str := self.Encode() + "\r\n"
	return []byte(str)
}

func (self *NMEA) Encode() string {
	self.CheckLngLatDDMM()
	var strNmea string = self.Type + "," + self.UTCStr + "," + self.B + "," + self.DirB + "," + self.L + "," + self.DirL + "," +
		self.Quality + "," + self.NumberOfSVs + "," + self.HDOP + "," +
		self.Height + "," + self.HeightU + "," + self.HeightGeoid + "," + self.HeightGeoidU + "," +
		self.DGPSAge + "," + self.SiteNo
	crc := CRC([]byte(strNmea))
	crcstr := utils.DecimalToAny(int(crc), 16, 2)
	strNmea = "$" + strNmea + "*" + crcstr
	return strNmea
}

func (self *NMEA) InitNMEA0183() {
	self.ParseText("$GNGGA,010918.00,3025.36715396,N,11427.95302424,E,1,12,0.9,0,M,0,M,1.0,0129*71")
}

func (self *NMEA) CheckLngLatDDMM() {
	if len(self.L) > 0 {
		idx := strings.IndexByte(self.L, '.')
		if idx < 5 {
			l0 := len(self.L) + (5 - idx)
			self.L = utils.AddPrefixForWidth(self.L, l0, "0")
		}
	}

}

func (self *NMEA) ParseText(str string) int {
	self.IsOK = false
	str = utils.Trim(str)
	if len(str) < 6 {
		return -1
	}

	if str[0] != '$' {
		return -1
	}

	if str[len(str)-3] != '*' {
		return -1
	}

	self.NMEAStr = str
	// 最后两位校验码
	self.CRCCode = str[len(str)-2:]

	// 丢掉前面$和后三位校验码
	str = str[1 : len(str)-3]

	strs := strings.Split(str, ",")
	if len(strs) < 15 {
		return -1
	}

	self.Type = strs[0]
	self.UTCStr = strs[1]
	self.B = strs[2]
	self.DirB = strs[3]
	self.L = strs[4]
	self.DirL = strs[5]
	self.Quality = strs[6]
	self.NumberOfSVs = strs[7]
	self.HDOP = strs[8]
	self.Height = strs[9]
	self.HeightU = strs[10]
	self.HeightGeoid = strs[11]
	self.HeightGeoidU = strs[12]
	self.DGPSAge = strs[13]
	self.SiteNo = strs[14]
	self.checkIsOK(str)

	self.CheckLngLatDDMM()

	return 0

}

func CalcuDisForNmea(strNmea1, strNmea2 string) float64 {
	nmea1 := NewNMEA()
	nmea2 := NewNMEA()
	nmea1.ParseText(strNmea1)
	nmea2.ParseText(strNmea2)
	return CalcuDistance(nmea1.GetLng(), nmea1.GetLat(), nmea2.GetLng(), nmea2.GetLat())

}
